// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <sys/time.h>

#ifndef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG  // nano resolution
#define BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG
#endif

#include <chrono>
#include <cstdint>
#include <string>
#include <string_view>
#include <unordered_map>  // for std::hash<std::string>
#include <utility>

#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/date_time/time_duration.hpp>

namespace impl {

using boost::posix_time::ptime;

// @TODO(jshin): this does not compile
// static_assert(
//     boost::posix_time::time_duration::resolution() == boost::date_time::time_resolutions::nano,
//     "boost ptime is not compiled for nano precision");
// static_assert(
//     boost::posix_time::time_duration::unit().total_nanoseconds() == 1,
//     "boost ptime is not compiled for nano precision");

std::string Iso8601FromTimestamp(int64_t ts);

std::string YmdFromTimestamp(int64_t ts);

ptime PtimeFromDatetime(
    int year,
    int month,
    int day,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int64_t nano = 0);

ptime PtimeFromTimestamp(int64_t timestamp);

int64_t TimestampToUtc0(int64_t timestamp);

ptime PtimeFromIso8601(const std::string& str);

int64_t TimestampFromPtime(ptime time);

int64_t TimestampFromIso8601(const std::string& str);

int64_t TimestampFromOkexIso8601(std::string_view str);

int64_t TimestampFromOkexIndexIso8601(std::string_view str);

int64_t TimestampFromFtxIso8601(std::string_view str);

int64_t TimestampFromGdaxIso8601(std::string_view str);

int64_t TimestampFromMexcIso8601(std::string_view str);

int64_t TimestampFromString(const std::string& str);

int64_t TimestampFromDatetime(
    int year,
    int month,
    int day,
    int hour = 0,
    int minute = 0,
    int second = 0,
    int64_t nano = 0);

int64_t DurationFromIso8601(const std::string& s);
int64_t DurationFromString(const std::string& s);

std::pair<int64_t, int64_t> IntervalFromIso8601(const std::string& s);

inline int64_t GetCurrentTimestamp() {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(
             std::chrono::system_clock::now().time_since_epoch())
      .count();
}

inline int64_t GetLastNDayTimestamp(size_t day) {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(
             std::chrono::system_clock::now().time_since_epoch())
             .count() -
         int64_t(24) * 60 * 60 * 1000'000'000 * day;
}

inline int64_t GetCurrentTimestampMs() {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(
             std::chrono::system_clock::now().time_since_epoch())
             .count() /
         1000'000;
}

inline int64_t GetLastNDayTimestampMs(size_t day) {
  return (std::chrono::duration_cast<std::chrono::nanoseconds>(
              std::chrono::system_clock::now().time_since_epoch())
              .count() -
          int64_t(24) * 60 * 60 * 1000'000'000 * day) /
         1000'000;
}

std::pair<int64_t, int64_t> RateFromIso8601(const std::string& s);
std::string DebugStringFromDuration(int64_t duration);

// Future Expiry
// TODO(donggu): delete these
int64_t Weekly(int64_t t, int hour);     // the first weekly expiry after t (exclusive)
int64_t Biweekly(int64_t t);             // the first biweekly expiry after t (exclusive)
int64_t Quarterly(int64_t t, int hour);  // the first quarterly expiry after t (exclusive)

}  // namespace impl

using impl::DebugStringFromDuration;
using impl::DurationFromIso8601;  // "P.." => int64
using impl::DurationFromString;   // "P.." or "1234" => int64
using impl::GetCurrentTimestamp;
using impl::GetCurrentTimestampMs;
using impl::GetLastNDayTimestamp;
using impl::GetLastNDayTimestampMs;
using impl::IntervalFromIso8601;  // "xx/yy" => pair<int64,int64>
using impl::Iso8601FromTimestamp;
using impl::PtimeFromDatetime;
using impl::PtimeFromIso8601;
using impl::PtimeFromTimestamp;
using impl::RateFromIso8601;  // "int/duration" =>
using impl::TimestampFromDatetime;
using impl::TimestampFromFtxIso8601;
using impl::TimestampFromGdaxIso8601;
using impl::TimestampFromIso8601;
using impl::TimestampFromOkexIndexIso8601;
using impl::TimestampFromOkexIso8601;
using impl::TimestampFromPtime;
using impl::TimestampFromString;  // "2019-.." (iso) or "1234" (epoch) => int64
using impl::TimestampToUtc0;
using impl::YmdFromTimestamp;
// TODO(donggu): delete these
using impl::Biweekly;
using impl::Quarterly;
using impl::Weekly;
